/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */
 
/**
 * @version package.c 0.0.1
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 *
 * Data package encoder & decoder
 */

#include "bspd.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <sys/file.h>

#ifdef HAVE_MALLOC_H
#ifndef __OpenBSD__
#include <malloc.h>
#endif
#endif

static int seed[8] = {13, -23, 37, -47, 53, -67, 79, -83};

/* Decode ouput string */
char * package_decode(char *input, int len, int space)
{
	int i;
	unsigned long long j;
	
	space = (space < 0) ? 0 : space;
	char *output = (char *) malloc(len + space);

	for (i = 0; i < len; i ++)
	{
		j = (unsigned long long) input[i];
		j -= (seed[i % 8]) * (i + 1);
		j = (j + 0x100) % 0x100;
		
		output[i + space] = (char) j;
	}

	return output;
}

/* Encode output string */
char * package_encode(char *input, int len, int space)
{
	int i;
	unsigned long long j;

	space = (space < 0) ? 0 : space;
	char *output = (char *) malloc(len + space);

	for (i = 0; i < len; i ++)
	{
		j = (unsigned long long) input[i];
		j += (seed[i % 8]) * (i + 1);
		j = (j + 0x100) % 0x100;
		
		output[i + space] = (char) j;
	}

	return output;
}

/* Pack datas */
char * package_pack(char **params, unsigned int plen, unsigned int * len)
{
	unsigned int i, curr;
	unsigned int psize = 0;
	unsigned long long pvalue;
	char *r = (char *) malloc(PACK_BUFFER_SIZE);
	char *new_buffer = (char *) NULL;
	int buffer_size = PACK_BUFFER_SIZE;
	
	// Write plen first, leave 4 bytes for command id
	put_u_int(plen, r + 4);
	
	curr = 8;
	
	for (i = 0; i < plen; i ++)
	{
		if (!params[i])
		{
			break;
		}
		
		// Check "ZERO" value
		if (0 == strcmp(params[i], "0"))
		{
			psize = 3;
			
			if (curr + psize > buffer_size)
			{
				// Enlarge buffer
				new_buffer = (char *) realloc(r, buffer_size * 2);
				if (new_buffer == NULL)
				{
					// Enlarge error, clean your memory...
					return r;
				}
				
				r = new_buffer;
				buffer_size *= 2;
			}
			
			r[curr] = 0x2;
			r[curr + 1] = 0x0;
			r[curr + 2] = 0x0;
			
			curr += psize;
			
			continue;
		}
		
		pvalue = strtoull(params[i], NULL, 10);
		
		if (0 == pvalue || strlen(params[i]) > floor(log10((double) pvalue)) + 1)
		{
			// String
			psize = strlen(params[i]);
			
			while (curr + psize + 5 > buffer_size)
			{
				// Enlarge buffer
				new_buffer = (char *) realloc(r, buffer_size * 2);
				if (new_buffer == NULL)
				{
					// Enlarge error, clean your memory...
					return r;
				}
				
				r = new_buffer;
				buffer_size *= 2;
			}
			
			r[curr] = 0xff;
			put_u_int((unsigned int) psize, r + curr + 1);
			
			memcpy(r + curr + 5, params[i], psize);
			
			curr += psize + 5;
			
			continue;
		}
		
		else
		{
			// Number
			if (pvalue <= 0xffff)
			{
				// Short
				psize = 3;
			}
			
			else if (pvalue <= 0xffffffff)
			{
				// Unsigned single
				psize = 5;
			}
			
			else
			{
				// Unsigned long
				psize = 9;
			}
			
			if (curr + psize > buffer_size)
			{
				// Enlarge buffer
				new_buffer = (char *) realloc(r, buffer_size * 2);
				if (new_buffer == NULL)
				{
					// Enlarge error, clean your memory...
					return r;
				}
				
				r = new_buffer;
				buffer_size *= 2;
			}
			
			if (psize == 3)
			{
				r[curr] = 0x2;
				put_u_short((short) pvalue, r + curr + 1);
			}
			
			else if (psize == 5)
			{
				r[curr] = 0x4;
				put_u_int((unsigned int) pvalue, r + curr + 1);
			}
			
			else
			{
				r[curr] = 0x8;
				put_u_ll(pvalue, r + curr + 1);
			}
			
			curr += psize;
			
			continue;
		}
	}
	
	*len = curr;
	
	return r;
}

/* Unpack input stream into array */
char ** package_unpack(char *input, unsigned int len, unsigned int *plen_v)
{
	unsigned int plen;
	unsigned int i, curr = 0;
	unsigned char tmp_t;
	unsigned int psize;
	char *pstr = (char *) NULL, t[32];
	unsigned long long pvalue;
	
	if (len < 4)
	{
		return (char **) NULL;
	}
	
	plen = get_u_int(input);
	*plen_v = plen;
	
	char **r = (char **) malloc(plen * sizeof(char *));
	curr += 4;
	
	for (i = 0; i < plen; i ++)
	{
		// Detect type first
		tmp_t = (unsigned char) input[curr];
		
		switch (tmp_t)
		{
			case 0x1 : 
				// Boolean
				if (len - curr < 2)
				{
					return (char **) NULL;
				}
				
				pstr = (char *) malloc(2);
				memset(pstr, 0, 2);
				pstr[0] = (input[curr + 1]) ? '1' : '0';
				
				curr += 2;
				
				break;
			
			case 0x2 : 
				// Short
				if (len - curr < 3)
				{
					return (char **) NULL;
				}
				
				pvalue = (unsigned long long) get_u_short(input + curr + 1);
				sprintf(t, "%llu", pvalue);
				pstr = strdup(t);
				
				curr += 3;
				
				break;
			
			case 0x4 : 
				// Unsigned int
				if (len - curr < 5)
				{
					return (char **) NULL;
				}
				
				pvalue = (unsigned long long) get_u_int(input + curr + 1);
				sprintf(t, "%llu", pvalue);
				pstr = strdup(t);
				
				curr += 5;
				
				break;
			
			case 0x8 : 
				// Unsigned long long
				if (len - curr < 9)
				{
					return (char **) NULL;
				}
				
				pvalue = get_u_ll(input + curr + 1);
				sprintf(t, "%llu", pvalue);
				pstr = strdup(t);
				
				curr += 9;
				
				break;
			
			case 0x20 : 
				// Float, convert to string
				// I don't like IEEE 754 now ~~~
				break;
			
			case 0xff : 
				// String
				if (len - curr < 5)
				{
					return (char **) NULL;
				}
				
				psize = get_u_int(input + curr + 1);
				
				if (len - curr - 5 < psize)
				{
					return (char **) NULL;
				}
				
				pstr = (char *) malloc(psize + 1);
				memcpy(pstr, input + curr + 5, psize);
				pstr[psize] = 0x0;
				
				curr += 5 + psize;
				
				break;
			
			default : 
				// Unsupported
				break;
		}
		
		r[i] = pstr;
	}
	
	return r;
}

/* Write a short to first 2 bytes */
void put_u_short(short id, char *src)
{
	if (src)
	{
		src[0] = id >> 8;
		src[1] = id % 0x100;
	}

	return;
}

/* Get a short from input */
short get_u_short(char *src)
{
	short id = 0;

	if (src)
	{
		id = (((unsigned char) src[0]) << 8) + 
			((unsigned char) src[1]);
	}

	return id;
}

/* Write an unsigned int to first 4 bytes */
void put_u_int(unsigned int id, char *src)
{
	if (src)
	{
		src[0] = id >> 24;
		src[1] = (id >> 16) % 0x100;
		src[2] = (id >> 8) % 0x100;
		src[3] = id % 0x100;
	}
	
	return;
}

/* Get an unsigned int from input */
unsigned int get_u_int(char *src)
{
	unsigned int id = 0;
	
	if (src)
	{
		id = (((unsigned char) src[0]) << 24) + 
			(((unsigned char) src[1]) << 16) + 
			(((unsigned char) src[2]) << 8) + 
			((unsigned char) src[3]);
	}
	
	return id;
}

/* Write an unsigned long long to first 8 bytes */
void put_u_ll(unsigned long long id, char *src)
{
	if (src)
	{
		src[0] = id >> 56;
		src[1] = (id >> 48) % 0x100;
		src[2] = (id >> 40) % 0x100;
		src[3] = (id >> 32) % 0x100;
		src[4] = (id >> 24) % 0x100;
		src[5] = (id >> 16) % 0x100;
		src[6] = (id >> 8) % 0x100;
		src[7] = id % 0x100;
	}

	return;
}
/* Get an unsigned long long from input */
unsigned long long get_u_ll(char *src)
{
	unsigned long long id = 0;

	if (src)
	{
		id = ((unsigned long long) ((((unsigned char) src[0]) << 24) + 
		    (((unsigned char) src[1]) << 16) + 
		    (((unsigned char) src[2]) << 8) + 
		    (((unsigned char) src[3]))) << 32) + 
		    (((unsigned char) src[4]) << 24) + 
		    (((unsigned char) src[5]) << 16) + 
		    (((unsigned char) src[6]) << 8) +
		    ((unsigned char) src[7]);
	}

	return id;
}

/* Output binary data, for debug usage */
void package_output(char *input, unsigned int len)
{
	unsigned int i;
	
	fprintf(stderr, "BINARY DATA OUTPUT (%u bytes) :\n", len);
	for (i = 0; i < len; i ++)
	{
		fprintf(stderr, " %02X", (unsigned char) input[i]);
		
		if (15 == i % 16)
		{
			fprintf(stderr, "\n");
		}
	}
	
	fprintf(stderr, "\n");
	
	return;
}

/* Generate an unique id */
void package_unique_id(char *input, unsigned int len)
{
	static int fd = -2;
	unsigned int seed = 0;
	struct timeval tm;

	if (fd == -2)
	{
		fd = open("/dev/urandom", O_RDONLY|O_NONBLOCK);

		if (fd == -1)
		{
			fd = open("/dev/random", O_RDONLY|O_NONBLOCK);
		}
	}

	if (fd < 0)
	{
		return;
	}

	int i, nbytes = len;

	while (nbytes > 0)
	{
		i = read(fd, input + (len - nbytes), nbytes);
		if (i <= 0)
		{
			continue;
		}

		nbytes -= i;
	}
	
	gettimeofday(&tm, NULL);
	seed = (getpid() << 0x10) ^ getuid() ^ tm.tv_sec ^ tm.tv_usec;

	for (i = 0; i < len ; i ++)
	{
		input[i] = rand_r(&seed) & 0xFF;
	}
	
	return;
}
